package arbor.mining.util.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.*;

import arbor.mining.util.UtilItem;

/**
 * WebPlainConn is used to connect a plain html page and cached it in memory.
 * You can set a tolerance time to avoid accessing the same page frequently.
 * Sometimes, different applications may access the same html pages at
 * similar time but such pages may not be updated in seconds. The class will 
 * be useful to save bandwidth and to avoid network delay issue. 
 * Note that the class uses the singleton pattern.
 * For string that you have known the pattern, you are suggested to use 
 * WebStringConn instead. 
 * I use WebPlainConn in stock tasks since they may access the same pages periodically.
 * 
 * @author ktchuang 
 */
public class WebPlainConn extends UtilItem {
	/**
	 * UPDATE_ALWAYS denotes to request the up_to_date connection of a page. A
	 * new connection to the remote site will be executed immediately.
	 */
	public static int UPDATE_ALWAYS = 1;
  
	/**
	 * UPDATE_WITH_TOLERANCE denotes to request the up_to_date connection of a
	 * page. If the last connection to the page has been created within the
	 * tolerance time, the date will be return without re-connecting to the
	 * remote site anymore.
	 */		
	public static int UPDATE_WITH_TOLERANCE = 2;	
	
	/**
	 * Web_Plain_DEBUG indicates whether the message would be reported.
	 */
	public static boolean Web_Plain_DEBUG = false;

	/*
	 * The below type of connection is used to determine the connection
	 * situtation that we least access a page.
	 */
	public static int CONNECTION_INITIAL = 0;

	public static int CONNECTION_SUCCESS = 1;

	public static int CONNECTION_SUCCESS_WITHIN_TOLERANCE = 2;

	public static int CONNECTION_FAIL_DUE_TO_TIMEOUT = 3;

	public static int DOUBLE_CONNECTION_FAIL = 4;

	public static int CONTENT_UNKNOWN_FAILURE = 5;

	@Override
	public Properties getInitProperty() {
		Properties p = new Properties();
		/*
		 * RECONNECT_TOLERANCE_TIME specifies the time without re-connecting. It
		 * will be disable if status is set as UPDATE_ALWAYS.
		 */		
		p.put("RECONNECT_TOLERANCE_TIME", "30");
    
		/*
		 * Setting the default timeout value for the connection to web. History:
		 * Connect to TSE usually timeout and queue threads. I give a try to force
		 * timeout not more than 20 secs (two time connections).
		 */
		p.put("CONNECTION_TIMEOUT_TIME", "30000");
		return p;
	}	
  
	class ContEntry {
		long least_time;
		String cont;
		int exception_type = 0;

		ContEntry(long time, String cont) {
			this.least_time = time;
			this.cont = cont;
			exception_type = WebPlainConn.CONNECTION_INITIAL; /*
																		 * default
																		 * is
																		 * CONNECTION_INITIAL
																		 */
		}

		ContEntry() {
			;
		}
	}

	private static WebPlainConn me = null;

	private HashMap connPool;

	private WebPlainConn() {
		super();
		connPool = new HashMap();
	}

	public static WebPlainConn getInstance() {		
		if (me == null) {
			/* For threading safe, we should enforce a lock. */
			synchronized(WebPlainConn.class) {
			  me = new WebPlainConn();
			}
		}
		return me;
	}

	/**
	 * return the connection of the requested page.
	 * 
	 * @param url
	 * @param request_type:
	 *            current request_type contains UPDATE_ALWAYS or
	 *            UPDATE_WITH_TOLERANCE
	 * @return
	 * @throws IOException
	 */
	public String getConnection(String url, int request_type) {
		String cont = null;
		if (connPool.containsKey(url)) {
			if (request_type == UPDATE_ALWAYS) {
				/* always force re-connection */
				ContEntry c_entry = getPageFromWeb(url);
				cont = c_entry.cont;
				insertPool(url, c_entry);
				c_entry.exception_type = WebPlainConn.CONNECTION_SUCCESS;
			} else if (request_type == UPDATE_WITH_TOLERANCE) {
				ContEntry c_entry = (ContEntry) connPool.get(url);
				long cur_time = (new Date()).getTime();

				if (checkWithinTolerance(cur_time, c_entry.least_time) == true) {
					if (Web_Plain_DEBUG) {
						logger.warn("Connection is within the tolerance: "
								+ url);
					}
					cont = c_entry.cont;
					c_entry.exception_type = CONNECTION_SUCCESS_WITHIN_TOLERANCE;
				} else {
					/* re-connect */
					if (Web_Plain_DEBUG) {
						logger.warn("Re-connect to " + url);
					}
					c_entry = getPageFromWeb(url);
					insertPool(url, c_entry);
					cont = c_entry.cont;
					c_entry.exception_type = WebPlainConn.CONNECTION_SUCCESS;
				}
			}
		} else {
			if (Web_Plain_DEBUG) {
				logger.warn("First getting " + url);
			}
			ContEntry c_entry = getPageFromWeb(url);
			insertPool(url, c_entry);
			cont = c_entry.cont;
		}
		return cont;
	}

	/**
	 * return the least time to access the page.
	 * 
	 * @param url
	 * @return
	 */
	public long getLeaseAccessTime(String url) {
		if (connPool.containsKey(url)) {
			return ((ContEntry) connPool.get(url)).least_time;
		} else {
			return -1;
    }
	}

	/**
	 * return the situtation that we least access the page.
	 * 
	 * @param url
	 * @return
	 */
	public int getLeastAccessSitutation(String url) {
		if (connPool.containsKey(url)) {
			return ((ContEntry) connPool.get(url)).exception_type;
		} else {
			return -1;
    }
	}

	/**
	 * Execute the connection_to_web for retriving the web content.
	 * 
	 * @param url
	 * @return
	 */
	private ContEntry getPageFromWeb(String url) {
		ContEntry entry = new ContEntry();
		StringBuffer buf = new StringBuffer();
		HttpURLConnection http = null;
		URL u = null;
		try {
			u = new URL(url);
			http = (HttpURLConnection) u.openConnection();
			http.connect();
			http.setConnectTimeout(Integer.parseInt(prop.getProperty("CONNECTION_TIMEOUT_TIME")));
		} catch (MalformedURLException e) {
			logger.error(e);
		} catch (IOException e) {
			logger.error(e);
		}
		// Read all of the text returned by the HTTP server
		BufferedReader in = null;
		try {
			entry.least_time = (new Date()).getTime();
			in = new BufferedReader(
					new InputStreamReader(http.getInputStream()));
		} catch (IOException e) {
			entry.exception_type = WebPlainConn.CONNECTION_FAIL_DUE_TO_TIMEOUT;
			logger.error("Connection Fail:" + url);
		}
		if (in == null) {
			/* connect fail!! */
			if (connPool.containsKey(url)) {
				entry.cont = ((ContEntry) connPool.get(url)).cont;
				entry.exception_type = WebPlainConn.CONNECTION_FAIL_DUE_TO_TIMEOUT;
			} else {
				entry.cont = null;
				entry.exception_type = WebPlainConn.CONNECTION_FAIL_DUE_TO_TIMEOUT;
			}
		} else {
			String htmlText;
			try {
				while ((htmlText = in.readLine()) != null) {
					// Keep in mind that readLine() strips the newline
					// characters
					buf.append(htmlText + "\n");
				}
				entry.cont = buf.toString();
				in.close();
			} catch (IOException e) {
				entry.exception_type = WebPlainConn.CONTENT_UNKNOWN_FAILURE;
				logger.error(e);
				/* connect fail!! */
				if (connPool.containsKey(url)) {
					entry.cont = ((ContEntry) connPool.get(url)).cont;
				}
			}
		}

		if (entry.exception_type == WebPlainConn.CONNECTION_INITIAL) {
			entry.exception_type = WebPlainConn.CONNECTION_SUCCESS;
		}
		if (Web_Plain_DEBUG == true)
			logger.error(url + ":" + entry.cont);
		http.disconnect();
		return entry;

	}

	/**
	 * Execute the connectino to web for retriving the web content.
	 * 
	 * @param url
	 * @return
	 */
	private ContEntry getPageFromWeb_old(String url) {
		ContEntry entry = new ContEntry();
		StringBuffer buf = new StringBuffer();
		HttpURLConnection http = null;
		URL u = null;
		try {
			u = new URL(url);
			http = (HttpURLConnection) u.openConnection();
			http.connect();
			http.setConnectTimeout(WebPlainConn.CONNECTION_FAIL_DUE_TO_TIMEOUT);
		} catch (MalformedURLException e) {
			logger.error(e);
		} catch (IOException e) {
			logger.error(e);
		}
		// Read all of the text returned by the HTTP server
		BufferedReader in = null;
		try {
			entry.least_time = (new Date()).getTime();
			in = new BufferedReader(
					new InputStreamReader(http.getInputStream()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			entry.exception_type = WebPlainConn.CONNECTION_FAIL_DUE_TO_TIMEOUT;
			logger.warn("Connection Fail:" + e);
			try {
				entry.least_time = (new Date()).getTime();
				in = new BufferedReader(new InputStreamReader(http
						.getInputStream()));
			} catch (IOException e1) {
				entry.exception_type = WebPlainConn.DOUBLE_CONNECTION_FAIL;
				logger.error("Connect Double Fail!!" + e1);
			}

		}
		if (in == null) {
			/* connect fail!! */
			if (connPool.containsKey(url)) {
				entry.cont = ((ContEntry) connPool.get(url)).cont;
				logger.error("connection fail" + url);
			}
		} else {
			String htmlText;

			try {
				while ((htmlText = in.readLine()) != null) {
					// Keep in mind that readLine() strips the newline
					// characters
					buf.append(htmlText + "\n");

				}
				entry.cont = buf.toString();
				in.close();
			} catch (IOException e) {
				entry.exception_type = WebPlainConn.CONTENT_UNKNOWN_FAILURE;
				logger.error(e);
				/* connect fail!! */
				if (connPool.containsKey(url)) {
					entry.cont = ((ContEntry) connPool.get(url)).cont;
				}

			}
		}
		if (entry.exception_type == WebPlainConn.CONNECTION_INITIAL) {
			entry.exception_type = WebPlainConn.CONNECTION_SUCCESS;
		}
		return entry;

	}

	/**
	 * Check if the interval between two access time is smaller than the
	 * tolerance time
	 * 
	 * @param cur_time
	 * @param prev_time
	 * @return
	 */
	private boolean checkWithinTolerance(long cur_time, long prev_time) {
		if ((cur_time - prev_time) >
        (1000 * Integer.parseInt(prop.getProperty("RECONNECT_TOLERANCE_TIME")))) {
			return false;
		} else {
			return true;
		}
	}

	private void insertPool(String url, ContEntry content) {
		if (!connPool.containsKey(url)) {
			synchronized (connPool) {
				connPool.put(url, content);
			}
		}
	}

	/**
	 * Enforce to clean the connection pool if there are numerous connections
	 * included in the pool. Connection which are old enough will be filtered
	 * out at first.
	 */
	public void cleanPool() {
		synchronized (connPool) {
			connPool.clear();
		}
	}
}
